#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/delay.h>

#define DEVICE_NAME "lcd_device"
#define CLASS_NAME "lcd_class"

#define RS_PIN 17
#define RW_PIN 18
#define E_PIN 27
#define D4_PIN 22
#define D5_PIN 23
#define D6_PIN 24
#define D7_PIN 25

static dev_t dev_num;
static struct cdev c_dev;
static struct class *cl;
static struct device *dev;

static int char_i = 0;
static char lcd_buf[16];

void lcd_send_command(unsigned char cmd)
{
	gpio_set_value(RS_PIN, 0);
	gpio_set_value(RW_PIN, 0);

	gpio_set_value(D4_PIN, (cmd >> 4) & 0x01);
	gpio_set_value(D5_PIN, (cmd >> 5) & 0x01);
	gpio_set_value(D6_PIN, (cmd >> 6) & 0x01);
	gpio_set_value(D7_PIN, (cmd >> 7) & 0x01);
	gpio_set_value(E_PIN, 1);
	udelay(50);
	gpio_set_value(E_PIN, 0);

	gpio_set_value(D4_PIN, cmd & 0x01);
	gpio_set_value(D5_PIN, (cmd >> 1) & 0x01);
	gpio_set_value(D6_PIN, (cmd >> 2) & 0x01);
	gpio_set_value(D7_PIN, (cmd >> 3) & 0x01);
	gpio_set_value(E_PIN, 1);
	udelay(50);
	gpio_set_value(E_PIN, 0);
}

void lcd_send_data(unsigned char data)
{
	gpio_set_value(RS_PIN, 1);
	gpio_set_value(RW_PIN, 0);

	gpio_set_value(D4_PIN, (data >> 4) & 0x01);
	gpio_set_value(D5_PIN, (data >> 5) & 0x01);
	gpio_set_value(D6_PIN, (data >> 6) & 0x01);
	gpio_set_value(D7_PIN, (data >> 7) & 0x01);
	gpio_set_value(E_PIN, 1);
	udelay(50);
	gpio_set_value(E_PIN, 0);

	gpio_set_value(D4_PIN, data & 0x01);
	gpio_set_value(D5_PIN, (data >> 1) & 0x01);
	gpio_set_value(D6_PIN, (data >> 2) & 0x01);
	gpio_set_value(D7_PIN, (data >> 3) & 0x01);
	gpio_set_value(E_PIN, 1);
	udelay(50);
	gpio_set_value(E_PIN, 0);
}

void lcd_clear_first_line(void)
{
	lcd_send_command(0x80 | 0x00);

	for (int i = 0; i < 16; i++)
	{
		lcd_send_data(0x20);
	}
}

void lcd_clear_second_line(void)
{
	lcd_send_command(0x80 | 0x40);

	for (int i = 0; i < 16; i++)
	{
		lcd_send_data(0x20);
	}
}

void lcd_init(void)
{
	gpio_request(RS_PIN, "RS");
	gpio_request(RW_PIN, "RW");
	gpio_request(E_PIN, "E");
	gpio_request(D4_PIN, "D4");
	gpio_request(D5_PIN, "D5");
	gpio_request(D6_PIN, "D6");
	gpio_request(D7_PIN, "D7");

	gpio_direction_output(RS_PIN, 0);
	gpio_direction_output(RW_PIN, 0);
	gpio_direction_output(E_PIN, 0);
	gpio_direction_output(D4_PIN, 0);
	gpio_direction_output(D5_PIN, 0);
	gpio_direction_output(D6_PIN, 0);
	gpio_direction_output(D7_PIN, 0);

	lcd_send_command(0x33);
	lcd_send_command(0x32);
	lcd_send_command(0x28);
	lcd_send_command(0x0c);
	lcd_send_command(0x06);
	lcd_send_command(0x01);
}

void lcd_print(const char *msg, int len)
{
	for (int j = 0; j < len - 1; j++)
	{
		if (char_i != 0 && (char_i % 32 == 0||(char_i>16&&char_i%16==0)))
		{
			// Move the second line to the first line
			lcd_send_command(0x80 | 0x00); // Set cursor to the first line (0x00)
			for (int k = 0; k < 16; k++)
			{
				lcd_send_data(lcd_buf[k]); // Read the second line and move it to the first line
			}
			lcd_clear_second_line();
		}


		if(char_i>=16){
			lcd_send_command(0xc0|(char_i%16));
		}else{
			lcd_send_command(0x80|(char_i%16));
		}

		lcd_buf[char_i % 16] = msg[j];
		char_i++;
		lcd_send_data(msg[j]);
	}
}

static int lcd_open(struct inode *inode, struct file *file)
{
	printk("lcd_device opened\n");
	return 0;
}

static int lcd_close(struct inode *inode, struct file *file)
{
	printk("lcd_device closed\n");
	return 0;
}

static ssize_t lcd_write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{
	char *kernel_buf;

	lcd_send_command(0x80);

	kernel_buf = kmalloc(len + 1, GFP_KERNEL);
	if (!kernel_buf)
	{
		return -ENOMEM;
	}

	if (copy_from_user(kernel_buf, buf, len))
	{
		kfree(kernel_buf);
		return -EFAULT;
	}
	kernel_buf[len] = '\0';

	lcd_print(kernel_buf, len);

	kfree(kernel_buf);
	return len;
}

static struct file_operations fops = {
	.open = lcd_open,
	.release = lcd_close,
	.write = lcd_write,
};

static int __init lcd_driver_init(void)
{
	int ret;

	lcd_init();
	ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
	if (ret < 0)
	{
		printk(KERN_ERR "Failed to allocate device number\n");
		return ret;
	}

	cl = class_create(THIS_MODULE, CLASS_NAME);
	if (IS_ERR(cl))
	{
		unregister_chrdev_region(dev_num, 1);
		return PTR_ERR(cl);
	}

	dev = device_create(cl, NULL, dev_num, NULL, DEVICE_NAME);
	if (IS_ERR(dev))
	{
		class_destroy(cl);
		unregister_chrdev_region(dev_num, 1);
		return PTR_ERR(dev);
	}

	cdev_init(&c_dev, &fops);
	ret = cdev_add(&c_dev, dev_num, 1);
	if (ret < 0)
	{
		device_destroy(cl, dev_num);
		class_destroy(cl);
		unregister_chrdev_region(dev_num, 1);
		return ret;
	}

	printk("LCD driver init\n");
	return 0;
}

static void __exit lcd_driver_exit(void)
{
	cdev_del(&c_dev);
	device_destroy(cl, dev_num);
	class_destroy(cl);
	unregister_chrdev_region(dev_num, 1);
	printk("LCD driver exit\n");
}

module_init(lcd_driver_init);
module_exit(lcd_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhuzonghui <puchuchen@outlook.com>");
MODULE_DESCRIPTION("this is lcd control. use gpio to control 1602A lcd and vss-gnd vdd-5v v0-resistance rs-17 rw-gnd e-27 d4~d7-22~25 a-5v k-gnd");
